package networkInterface;

import java.awt.Color;
import java.util.ArrayList;

import logging.Logger;

import object.Connection;
import object.Node;

import exception.NoConnectionPossibleException;

public abstract class NetworkInterfaceBase {
	protected Color defaultColor;
	private int maxConnections = 1;
	
	public NetworkInterfaceBase(Color c, Node node){
		this.defaultColor = c;
		this.node = node;
	}
	
	protected ArrayList<Connection> connections = new ArrayList<Connection>();
	protected Node node;
	/**
	 * This method adds a connection from n to this, as well as this to n.
	 * @param n The other NetworkInterface to add
	 * @throws NoConnectionPossibleException
	 * @throws IllegalStateException
	 */
	public abstract void addConnection(NetworkInterfaceBase n) throws NoConnectionPossibleException, IllegalStateException;
	
	/**
	 * <b>Should be used carefully, does not check preconditions</b>
	 * @param c Connection to add
	 */
	public void addConnectionWithoutCheck(Connection c){
		connections.add(c);
	}
	
	/**
	 * @return All connections on this interface
	 */
	public ArrayList<Connection> getConnections() {
		return connections;
	}

	/**
	 * @return Node that this interface is on
	 */
	public Node getNode() {
		return node;
	}
	/**<b>WARNING: Changing Node after this has been set has undefined behavior</b>
	 * @param node Node to add this interface to
	 */
	public void setNode(Node node) {
		this.node = node;
	}
	
	public int getNumberOfConnections()
	{
		return maxConnections;
	}
	
	/**
	 * @return True if there are connections available(without looking at any other objects)
	 */
	public boolean hasConnectionsAvailible(){
		if(node==null)
			return false;
		if(connections.size() < maxConnections)
			return true;
		return false;
	}
	/**
	 * @param n
	 * @return True if the two interfaces can connect
	 */
	public boolean canConnect(NetworkInterfaceBase netInt){		
		if(canConnectWithReason(netInt)==null)
			return true;
		return false;
	}
	
	/**
	 * @param n
	 * @return True if this interface is directly connected to n
	 */
	public boolean isDirectlyConnected(NetworkInterfaceBase netInt){
		for(Connection c : connections)
			if(c.isMember(netInt))
				return true;
		return false;
	}
	
	/**
	 * @param nodes the full arraylist of nodes
	 * @return An arraylist of network interfaces that this can connect to
	 */
	public ArrayList<NetworkInterfaceBase> getConnectableInterfaces(ArrayList<Node> nodes) {
		ArrayList<NetworkInterfaceBase> ints = new ArrayList<NetworkInterfaceBase>();
		for(Node n : nodes){
			for(NetworkInterfaceBase i : n.getInterfaces()){
				if(this.canConnect(i)){
					ints.add(i);
				}
			}
		}
		return ints;
	}
	
	/**
	 * Removes all connections from this interface and the interface that the connection is to.
	 * 
	 */
	public void removeConnections(){
		for(;connections.size()>0;){
			connections.get(0).getOtherNetworkInterface(this).removeConnection(connections.get(0));
		}
		connections.clear();
	}
	
	public void removeUnsafe(Connection c){
		if(!connections.remove(c))
			Logger.getInstance().log(Logger.WARNING, "Shouldn't happen: didn't remove connection unsafe");
	}
	
	public void removeConnection(Connection c){
		if(!connections.remove(c)){
			Logger.getInstance().log(Logger.SEVERE, "Shouldn't happen: didn't remove connection");
			return;
		}
		c.getOtherNetworkInterface(this).removeUnsafe(c);
		c.setInterfaces(null, null);
	}
	
	public Color getDefaultColor(){
		return defaultColor;
	}
	
	/**
	 * @param netInt Network interface to try to connect to
	 * @return Null if can connect, otherwise reason that connection is unavailable.
	 */
	public abstract String canConnectWithReason(NetworkInterfaceBase netInt);
	
	/**
	 * This function will get all connectable interfaces with no regard for current usage
	 * @return ArrayList of NetworkInterfaces that this Network interface could connect to if they had possible connections(or if they already do)
	 */
	public ArrayList<NetworkInterfaceBase> getPossiblyConnectableInterfaces(ArrayList<Node> nodes){
		ArrayList<NetworkInterfaceBase> ret = new ArrayList<NetworkInterfaceBase>();
		for(Node n : nodes){
			for(NetworkInterfaceBase i : n.getInterfaces()){
				if(this.canConnect(i) || this.canConnectWithReason(i).equalsIgnoreCase("No connections left")){
					ret.add(i);
				}
			}
		}
		return ret;
	}
	
	public ArrayList<Node> getDirectlyConnected(){
		ArrayList<Node> ret = new ArrayList<Node>();
		for(Connection c : connections){
			ret.add(c.getOtherNetworkInterface(this).getNode());
		}
		return ret;
	}


}
